import axios, { type AxiosInstance } from 'axios';
import type { IRequestConfig, IAxiosResponse, IAxiosError, Response } from '@/type/http';
import { getToken } from '@/utils/auth';
import { ElMessage } from 'element-plus';
import { useMainStore } from '@/stores';
import router from '@/router';

const HTTP_MESSAGE = {
	TOKEN_EXPIRED: '登录超时，请重新登录',
	ERROR_404: '404，抱歉页面出错了',
	ERROR_PATH: '抱歉，页面出错了',
	ERROR_NET: '网络错误，请稍后再试'
};
class HttpRequest {
	// 声明一个 Map 用于存储每个请求的标识 和 取消函数
	public requestPending = new Map();
	private axiosIns: AxiosInstance; // axios实例
	private axiosRequestConfig: IRequestConfig = {}; // axios 配置
	private isErrorMsg = false; // 防止多次显示返回的多个错误信息
	private baseURL;

	constructor(baseURL?: string | Function) {
		this.axiosRequestConfig.timeout = 1000 * 60 * 10; // 全局超时时限
		if (baseURL && baseURL instanceof String) {
			this.axiosRequestConfig.baseURL = baseURL as string;
		}
		this.baseURL = baseURL;
		this.axiosIns = axios.create(this.axiosRequestConfig); // 实例axios
		this.axiosIns.interceptors.request.use(this.onRequest.bind(this)); // 请求拦截
		this.axiosIns.interceptors.response.use(this.onResponseSuccess.bind(this), this.onResponseError.bind(this)); // 响应拦截
	}

	/**
	 * 添加请求
	 * @param {Object} config
	 */
	addPending = (config: IRequestConfig) => {
		// 获构建请求唯一标识
		const url = [config.method, config.url].join('&');
		config.cancelToken =
			config.cancelToken ||
			new axios.CancelToken(cancel => {
				if (!this.requestPending.has(url)) {
					// 如果 pending 中不存在当前请求，则添加进去
					this.requestPending.set(url, cancel);
				}
			});
	};

	/**
	 * 移除请求
	 * @param {Object} config
	 */
	removePending = (config: IRequestConfig) => {
		const url = [config.method, config.url].join('&');
		if (this.requestPending.has(url)) {
			// 如果在 pending 中存在当前请求标识，需要取消当前请求，并且移除
			const cancel = this.requestPending.get(url);
			cancel && cancel(url);
			this.requestPending.delete(url);
		}
	};

	/**
	 * 三秒之内的多个接口错误信息，只显示一个错误信息
	 */
	messageError = (msg?: string | undefined) => {
		if (!this.isErrorMsg) {
			this.isErrorMsg = true;
			ElMessage.error(msg || HTTP_MESSAGE.ERROR_NET);
			setTimeout(() => {
				this.isErrorMsg = false;
			}, 3000);
		}
	};
	// 跳转至登录页
	toLogin = () => {
		setTimeout(() => {
			router.push('/login');
		}, 300);
	};
	/**
	 * axios 请求拦截封装
	 * @param request
	 */
	private async onRequest(request: IRequestConfig): Promise<IRequestConfig> {
		if (request.isCancel) {
			// 在请求开始前，对之前的请求做检查取消操作
			this.removePending(request);
			// 添加请求到请求队列
			this.addPending(request);
		}

		// 始终用最新的token
		const token = getToken();
		request.headers!['token'] = token;
		const store = useMainStore();
		if (store) {
			// 设置请求头
			const { projectInfo } = store;
			request.headers!['project-id'] = projectInfo._id;
			request.headers!['group-code'] = projectInfo.code;
		}
		if (this.baseURL && this.baseURL instanceof Function) {
			request.baseURL = this.baseURL() || '';
		}

		const addHeaders = (request.addHeaders as { [key: string]: string }) || {};

		// 需要额外加header的接口
		if (addHeaders && Object.keys(addHeaders).length > 0) {
			for (const key in addHeaders) {
				if (Object.prototype.hasOwnProperty.call(addHeaders, key)) {
					request.headers![key] = addHeaders[key];
				}
			}
		}
		return request;
	}

	/**
	 * axios 响应成功拦截封装
	 * 响应拦截思路：
	 * @param response
	 */
	private onResponseSuccess(response: IAxiosResponse): Promise<IAxiosResponse> | unknown {
		const { config, status, data: res } = response;

		if (config.isCancel) {
			this.removePending(config); // 在请求结束后，移除本次请求
		}
		// 接口返回200
		const { code } = res;

		// code规则
		if (code && typeof code === 'number') {
			switch (code) {
				case 200:
					return Promise.resolve(res);
				case 401:
				case 403:
					this.messageError(HTTP_MESSAGE.TOKEN_EXPIRED);
					this.toLogin();
					break;
				default:
					this.messageError(res.message);
					break;
			}
			return Promise.reject();
		}

		if ((status >= 200 && status < 300) || status === 304) {
			return res;
		}

		if (status === 401 || status === 403) {
			this.messageError(HTTP_MESSAGE.TOKEN_EXPIRED);
			this.toLogin();
			return Promise.reject();
		}
		if (status === 404) {
			this.messageError(HTTP_MESSAGE.ERROR_404);
			return Promise.reject();
		}
		this.messageError(res.message || HTTP_MESSAGE.ERROR_NET);
		return Promise.reject();
	}

	/**
	 * axios 响应错误拦截封装
	 * @param error
	 */
	private onResponseError(error: IAxiosError) {
		const { config, response } = error;
		if (axios.isCancel(error)) {
			this.removePending(config || {}); // 从pendingRequest对象中移除请求
		} else {
			switch (response?.status) {
				case 401:
				case 403:
					this.messageError(HTTP_MESSAGE.TOKEN_EXPIRED);
					this.toLogin();
					return Promise.reject(error);
				default:
					break;
			}
			const { data } = response as { data: { message?: string } };
			// 三秒之内的多个接口错误信息，只显示一个错误信息
			this.messageError(data?.message || '');
		}
		return Promise.reject(error);
	}

	public get<R = Response>(url: string, config?: IRequestConfig): Promise<R> {
		return this.axiosIns.get(url, config);
	}

	public post<R = Response>(url: string, data?: unknown, config?: IRequestConfig): Promise<R> {
		return this.axiosIns.post(url, data, config);
	}

	public put<R = Response>(url: string, data?: unknown, config?: IRequestConfig): Promise<R> {
		return this.axiosIns.put(url, data, config);
	}

	public delete<R = Response>(url: string, config?: IRequestConfig): Promise<R> {
		return this.axiosIns.delete(url, config);
	}

}

export const http = new HttpRequest();
